package com.tsd.system.service.impl;

import cn.hutool.crypto.SecureUtil;
import cn.hutool.db.Entity;
import com.alibaba.fastjson.JSON;
import com.tsd.CommonMsgNameConstants;
import com.tsd.core.annotation.TaskJob;
import com.tsd.core.annotation.TaskJobMethod;
import com.tsd.core.config.FlywayConfig;
import com.tsd.core.constants.Constants;
import com.tsd.core.datasource.*;
import com.tsd.core.thread.ThreadPoolTaskUtil;
import com.tsd.core.utils.*;
import com.tsd.core.vo.HlpException;
import com.tsd.core.vo.HttpResult;
import com.tsd.core.vo.PageBean;
import com.tsd.core.vo.TableClass;
import com.tsd.job.QuartzJob;
import com.tsd.job.entity.SysTaskJobExt;
import com.tsd.service.BaseServiceImpl;
import com.tsd.system.dao.SqlSelectMapper;
import com.tsd.system.dao.SysAccountSetMapper;
import com.tsd.system.dao.SysResourceMapper;
import com.tsd.system.entity.*;
import com.tsd.system.service.SysAccountSetService;
import com.tsd.system.service.SysLogInfoService;
import com.tsd.system.service.SysUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

import static com.tsd.core.constants.Constants.VALUE_SUCCESS;

/**
 * 描述：SysAccountSet 服务实现层
 *
 * @author huawei
 * @date 2021/09/02 21:46:17
 */
@Configuration
@Service
public class SysAccountSetServiceImpl extends BaseServiceImpl implements SysAccountSetService {

    @Resource
    private SqlSelectMapper sqlSelectMapper;
    @Resource
    private QuartzJob quartzJob;
    @Resource
    private SysAccountSetMapper sysAccountSetMapper;
    @Resource
    private SysUserService sysUserService;
    @Resource
    private SysResourceMapper sysResourceMapper;
    @Resource
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
    @Resource
    private SysLogInfoService sysLogInfoService;
    @Resource
    private DataSourceInitService dataSourceInitService;
    @Resource
    private FlywayConfig flywayConfig;

    private static final Logger LOGGER = LoggerFactory.getLogger("quartzLogger");

    @Override
    public SysAccountSet getSysAccountSetById(Long id) throws Exception {
        return sysAccountSetMapper.selectByPrimaryKey(id);
    }

    @Override
    public SysAccountSetExt queryBySid(String sid) {
        return sysAccountSetMapper.selectBySid(sid);
    }

    @Override
    public void deleteSysAccountSets(List<String> list, SysUser opUser) throws Exception {
        for (String sid : list) {
            SysAccountSet old = sysAccountSetMapper.selectBySid(sid);
            super.checkEmpty(old, "该记录不存在:" + sid);
            sysAccountSetMapper.deleteByPrimaryKey(old.getId());
            try {
                //删除数据库和日志库
                String formatSql = "DROP DATABASE IF EXISTS `%s`";
                String sql = String.format(formatSql, old.getDb_name());
                sqlSelectMapper.insert(sql);
                String logDb = old.getDb_name() + Constants.DATABASE_SUFFIX_FOR_LOG;
                String sql_log = String.format(formatSql, logDb);
                sqlSelectMapper.insert(sql_log);
            } catch (Exception e) {
                LOGGER.error("删除账套出错：\n" + ExceptionUtil.getStackTraceFromException(e));
            }
        }
    }

    static String Code_Reg = "[0-9A-Za-z_] {3,50}";

    @Override
    public void saveSysAccountSet(SysAccountSetExt record, SysUser opUser) throws Exception {
        super.checkEmpty(record.getCode(), "账套编码不能为空");
        SysAccountSetExt dup = sysAccountSetMapper.selectDumpObj(record);
        super.checkNotEmpty(dup, "已存在该编码的账套");
        if (Pattern.matches(Code_Reg, record.getCode())) {
            throw new HlpException("账套编码必须是3-50位的数字、字母或下划线组成");
        }
        if (HlpUtils.isEmpty(record.getMode())){
            record.setMode("[]");
        }
        List<String> models = JSON.parseArray(record.getMode(),String.class);
        if (models.indexOf(Constants.MODE_TSD) == -1){
            models.add(Constants.MODE_TSD);
            record.setMode(JSON.toJSONString(models));
        }
        record.setDb_url(record.genDbUrl(true));
        if (HlpUtils.isEmpty(record.getId())) {
            this.checkAccountSet(record);
            super.autoInjectBaseData(record, opUser, TYPE_CREATE);
            //使用code作为sid
            record.setSid(record.getCode());
            record.setState(Constants.STATE_DB_NOT_CREATED);
            sysAccountSetMapper.insertSelective(record);
        } else {
            // 处理模块变更
            this.procModeChange(record);
            super.autoInjectBaseData(record, opUser, TYPE_UPDATE);
            if (Constants.STATE_EXPIRED == record.getState()) {
                //如果原来已过期，重新设置到期日，改下状态
                if (record.getExpired_date() != null && record.getExpired_date().compareTo(HlpUtils.getTheDate(new Date())) >= 0) {
                    record.setState(Constants.STATE_DB_CREATED);
                }
            }
            sysAccountSetMapper.updateByPrimaryKeySelective(record);
        }
    }

    /**
     * mysql的自动创建数据库（没有时创建）
     *
     * @param accountSetExt
     * @throws Exception
     */
    private void checkAccountSet(SysAccountSetExt accountSetExt) throws Exception {
        Connection connection = null;
        try {
            connection = JdbcUtil.getConnection(accountSetExt.getDriver(), accountSetExt.genDbUrl(false), accountSetExt.getDb_user_name(), accountSetExt.getDb_password());
        } catch (Exception e) {
            throw new HlpException("数据库连接失败：" + e.getMessage());
        }
        if (connection == null) {
            throw new HlpException("新账套数据库连接失败，请检查数据访问url、用户名、密码是否都正确");
        }
        // 检查数据库是否存在，若数据库不存在则通过检验
        List<String> dbList = JdbcUtil.getSchemas(connection, accountSetExt.getDb_name());
        if (!HlpUtils.isEmptyList(dbList)) {
            // 若数据库存在，则判断数据库是否为空；如果是空数据库则通过检验
            List<TableClass> classList = JdbcUtil.getTables(connection, accountSetExt.getDb_name(), null);
            if (!HlpUtils.isEmptyList(classList)) {
                throw new HlpException("已存在该名称的数据库，无法保存");
            }
        }
        JdbcUtil.releaseConnection(connection);
    }

    /**
     * 处理模块变更
     *
     * @param accountSetExt
     * @throws Exception
     */
    private void procModeChange(SysAccountSetExt accountSetExt) throws Exception {
        SysAccountSet oldData = sysAccountSetMapper.selectByPrimaryKey(accountSetExt.getId());
        super.checkEmpty(oldData, "账套不存在");
        List<String> oldModeList = JSON.parseArray(oldData.getMode(), String.class);
        List<String> modeList = JSON.parseArray(accountSetExt.getMode(), String.class);
        ThreadPoolTaskUtil.execute(() -> {
            DataSourceContextHolder.setDataSourceType(accountSetExt.getSid());
            // 获取需要删除的模块
            List<String> delModeList = ListUtil.filter(oldModeList, t -> !modeList.contains(t));
            if (!HlpUtils.isEmptyList(delModeList)) {
                // 删除资源记录
                boolean hasTsd = ListUtil.anyMatch(delModeList, "tsd"::equals);
                if (hasTsd) {
                    delModeList = ListUtil.filter(delModeList, t -> !"tsd".equals(t));
                    delModeList.add("base");
                }
                sysResourceMapper.updateResource4Mode(delModeList, 1);
            }
            // 获取需要新增的模块
            List<String> addModeList = ListUtil.filter(modeList, t -> !oldModeList.contains(t));
            if (!HlpUtils.isEmptyList(addModeList)) {
                // 删除资源记录
                boolean hasTsd = ListUtil.anyMatch(addModeList, "tsd"::equals);
                if (hasTsd) {
                    addModeList = ListUtil.filter(addModeList, t -> !"tsd".equals(t));
                    addModeList.add("base");
                }
                sysResourceMapper.updateResource4Mode(addModeList, 0);

                DataSourceContextHolder.setDataSourceType(null);
                FlywayUtil.copyJarFlyway2Classpath();
                List<String> list = ListUtil.filter(modeList, t -> !oldModeList.contains(t));
                SysAccountSetExt setExt = accountSetExt.copy4Update(list);
                if (!DynamicDataSource.checkExist(accountSetExt.getSid())) {
                    Entity entity = new Entity(Constants.TABLE_NAME_SYS_ACCOUNT_SET).parseBean(accountSetExt);
                    dataSourceInitService.initDataSource(entity);
                }
                Entity updateEntity = new Entity(Constants.TABLE_NAME_SYS_ACCOUNT_SET).parseBean(setExt);
                Map<Object, Object> dynamicDataSourceMap = DynamicDataSource.getTargetDataSource();
                DataSource dataSource = (DataSource) dynamicDataSourceMap.get(setExt.getSid());
                FlywayUtil.procSqlFile2AccountSet4Mode(dataSource, updateEntity);
                flywayConfig.doFlyway(updateEntity, dataSource);
                FlywayUtil.clearJarFlywayFromClasspath();
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    LOGGER.error("延时失败：" + e.getMessage());
                }
                try {
                    this.initAccountSetAdmin(accountSetExt.getCode());
                } catch (Exception e) {
                    LOGGER.error("初始化账套管理员出错：" + e.getMessage());
                    LOGGER.error(ExceptionUtil.getStackTraceFromException(e));
                }
            }
        });
    }

    /**
     * 创建数据库，采用synchronized同步锁
     *
     * @param t
     * @param reset 是否重置，只是影响相关信息提示，不影响处理逻辑
     * @throws Exception
     */
    @Override
    synchronized public void createDataBase(SysAccountSetExt t, boolean reset) throws Exception {
        SysAccountSetExt old = sysAccountSetMapper.selectBySid(t.getSid());
        super.checkEmpty(old, "账套不存在");
        SysAccountSetExt doing = sysAccountSetMapper.selectCreating();
        if (doing != null) {
            return;
        }
        threadPoolTaskExecutor.execute(() -> {
            DataSourceContextHolder.setDataSourceType(null);
            SysAccountSetExt toCreate = sysAccountSetMapper.select4Create();
            while (toCreate != null) {
                toCreate.setState(Constants.STATE_DB_CREATING);
                sysAccountSetMapper.updateByPrimaryKeySelective(toCreate);
                long startTime = System.currentTimeMillis();
                SysLogInfoExt infoExt = new SysLogInfoExt();
                try {
                    // 1、创建数据库
                    String logDb = toCreate.getDb_name() + Constants.DATABASE_SUFFIX_FOR_LOG;
                    String formatSql = "DROP DATABASE IF EXISTS `%s`; CREATE DATABASE IF NOT EXISTS `%s` CHARACTER  SET utf8mb4;";
                    String sql = String.format(formatSql, toCreate.getDb_name(), toCreate.getDb_name());
                    sqlSelectMapper.insert(sql);
                    String sql_log = String.format(formatSql, logDb, logDb);
                    sqlSelectMapper.insert(sql_log);
                    Thread.sleep(1000);
                    // 2、初始化数据源
                    Entity entity = new Entity(Constants.TABLE_NAME_SYS_ACCOUNT_SET).parseBean(toCreate);
                    dataSourceInitService.initDataSource(entity);
                    // 3、还原数据库
                    Map<Object, Object> dynamicDataSourceMap = DynamicDataSource.getTargetDataSource();
                    Set<Object> keys = dynamicDataSourceMap.keySet();
                    Map<Object, Object> targetDataSourceMap = new ConcurrentHashMap<>();
                    for (Object key : keys) {
                        if (key.toString().startsWith(toCreate.getSid())) {
                            targetDataSourceMap.put(key, dynamicDataSourceMap.get(key));
                        }
                    }
                    Map<Object, String> ret = flywayConfig.migrateFlyway(targetDataSourceMap);
                    String retValue = ret.get(toCreate.getSid());
                    if (VALUE_SUCCESS.equals(retValue)) {
                        FlywayUtil.clearSqlFile(entity);
                        toCreate.setState(Constants.STATE_DB_CREATED);
                    } else {
                        String msg = reset ? "手动重置账套" + retValue : "自动创建账套" + retValue;
                        setStateAndExceptionInfo(toCreate, infoExt, msg);
                    }
                } catch (Exception e) {
                    if (e instanceof org.flywaydb.core.internal.command.DbMigrate.FlywayMigrateException) {
                        org.flywaydb.core.internal.command.DbMigrate.FlywayMigrateException e1 = (org.flywaydb.core.internal.command.DbMigrate.FlywayMigrateException) e;
                    }
                    String msg = ExceptionUtil.getStackTraceFromException(e);
                    msg = reset ? "手动重置账套" + msg : "自动创建账套" + msg;
                    setStateAndExceptionInfo(toCreate, infoExt, msg);
                }
                toCreate.setCreate_end(new Date());
                sysAccountSetMapper.updateByPrimaryKeySelective(toCreate);
                try {
                    infoExt.setDuration_ms((int) (System.currentTimeMillis() - startTime));
                    infoExt.setLog_time(new Date(startTime));
                    infoExt.setOperation(reset ? "手动重置账套" : "自动创建账套");
                    infoExt.setParams_data(JSON.toJSONString(toCreate));
                    String logType = DataSourceContextHolder.DEFAULT_ACCOUNT_SET_SID + Constants.DATABASE_SUFFIX_FOR_LOG;
                    DataSourceContextHolder.setDataSourceType(logType);
                    sysLogInfoService.saveSysLogInfo(infoExt);
                } catch (Exception e) {
                    LOGGER.error(ExceptionUtil.getStackTraceFromException(e));
                    LOGGER.error("保存日志对象：" + JSON.toJSONString(infoExt));
                }
                if (Constants.STATE_DB_CREATED == toCreate.getState()) {
                    //创建账套后自动建立最近2个月的日志表
                    quartzJob.createTableLogInfo4AccountSet(toCreate.getSid());
                }
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    LOGGER.error("延时失败：" + e.getMessage());
                }
                try {
                     this.initAccountSetAdmin(toCreate.getCode());
                } catch (Exception e) {
                    LOGGER.error("初始化账套管理员出错：" + e.getMessage());
                    LOGGER.error(ExceptionUtil.getStackTraceFromException(e));
                }
                DataSourceContextHolder.setDataSourceType(null);
                toCreate = sysAccountSetMapper.select4Create();
            }
        });
    }

    /**
     * 初始化账套的管理员账号
     *
     * @param code
     */
    private void initAccountSetAdmin(String code) throws Exception {
        DataSourceContextHolder.setDataSourceType(code);
        String pwd = HlpUtils.doMd5(SecureUtil.md5(code));
        sysAccountSetMapper.initAccountSetAdmin(code, pwd);
    }

    private void setStateAndExceptionInfo(SysAccountSetExt accountSetExt, SysLogInfoExt infoExt, String msg) {
        accountSetExt.setState(Constants.STATE_DB_CREATED_FAIL);
        accountSetExt.setFlyway_except(msg);
        infoExt.setResult_msg(msg);
        LOGGER.error(msg);
    }

    @Override
    public List<SysAccountSetExt> findSysAccountSets(Map<String, Object> params, PageBean page) throws Exception {
        return sysAccountSetMapper.selectByPage(params, page);
    }

    @Override
    public List<SysAccountSetExt> findSysAccountSets(Map<String, Object> params) throws Exception {
        return sysAccountSetMapper.selectByParams(params);
    }

    @Override
    public List<SysTaskJobExt> getTaskJobList() {
        List<SysTaskJobExt> list = new ArrayList<>();
        ApplicationContext context = SpringContextUtil.getApplicationContext();
        Map<String, Object> map = context.getBeansWithAnnotation(TaskJob.class);
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();
            TaskJob taskJob = AnnotationUtils.findAnnotation(clazz, TaskJob.class);
            if (taskJob == null) {
                continue;
            }
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                if (method.isAnnotationPresent(TaskJobMethod.class)) {
                    TaskJobMethod jobMethod = method.getAnnotation(TaskJobMethod.class);
                    if (jobMethod == null) {
                        continue;
                    }
                    SysTaskJobExt jobExt = new SysTaskJobExt();
                    jobExt.setMode(taskJob.mode());
                    jobExt.setJob_object_name(entry.getKey());
                    jobExt.setJob_method_name(method.getName());
                    list.add(jobExt);
                }
            }
        }
        return list;
    }

    @Override
    public void executeUpdateSchema(List<String> sidList, SysUser opUser) throws Exception {
        SysAccountSetExt params = new SysAccountSetExt();
        params.setDr(0);
        params.setSid_list(sidList);
        List<SysAccountSetExt> list = sysAccountSetMapper.selectByEntity(params);
        if (HlpUtils.isEmptyList(list)) {
            return;
        }
        ThreadPoolTaskUtil.executeInList4Split(list, true, 1, itemList -> {
            SysAccountSetExt accountSetExt = itemList.get(0);
            // 2、初始化数据源
            Entity entity = new Entity(Constants.TABLE_NAME_SYS_ACCOUNT_SET).parseBean(accountSetExt);
            dataSourceInitService.initDataSource(entity);
            // 3、还原数据库
            Map<Object, Object> dynamicDataSourceMap = DynamicDataSource.getTargetDataSource();
            Set<Object> keys = dynamicDataSourceMap.keySet();
            Map<Object, Object> targetDataSourceMap = new ConcurrentHashMap<>();
            for (Object key : keys) {
                if (key.toString().startsWith(accountSetExt.getSid())) {
                    targetDataSourceMap.put(key, dynamicDataSourceMap.get(key));
                }
            }
            flywayConfig.migrateFlyway(targetDataSourceMap);
        });
    }

    @Override
    public void saveSysAccountSetAttribute(SysAccountSetExt t, SysUser opUser) throws Exception {
        SysAccountSetExt old = sysAccountSetMapper.selectBySid(t.getSid());
        super.checkEmpty(old, "账套不存在");
        sysAccountSetMapper.updateAttributeBySid(t);
    }

    @Override
    public HttpResult<SysAccountSetExt> checkSysAccountSetValid(String sid) {
        SysAccountSetExt accountSetExt = this.queryBySid(sid);
        if (accountSetExt == null || !accountSetExt.is_valid()) {
            return HttpResult.procError(CommonMsgNameConstants.getAccountStateError(accountSetExt));
        }
        return HttpResult.success(accountSetExt);
    }
}
